package com.boe.common.util;

import com.google.common.base.Strings;
import lombok.SneakyThrows;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.Temporal;
import java.util.*;
import java.util.stream.Collectors;

import lombok.*;

import static org.springframework.util.ClassUtils.getDefaultClassLoader;

public class ClassUtil {

    public static Boolean existsClass(String className) {
        try {
            getDefaultClassLoader().loadClass(className);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    /**
     * 判断某个类是否是指定的类型
     *
     * @param clazz
     * @param value
     * @return
     */
    public static Boolean isType(Class clazz, String value) {
        if (clazz.getTypeName().equals(value) || clazz.getSimpleName().equals(value)) return true;


        var superClass = clazz.getSuperclass();
        if (superClass != null) {
            return isType(superClass, value);
        }
        return false;
    }

    public static Boolean isCollectionType(Class clazz) {
        return Collection.class.isAssignableFrom(clazz);
    }


    public static Boolean isNumberType(Class clazz) {
        if (clazz.isPrimitive()) {
            if (clazz == Integer.class) return true;
            if (clazz == Float.class) return true;
            if (clazz == Double.class) return true;
            if (clazz == Long.class) return true;
            if (clazz == Short.class) return true;
            if (clazz == Byte.class) return true;
        }

        if (Number.class.isAssignableFrom(clazz)) {
            return true;
        }

        return false;
    }


    /**
     * 判断是否是 EnumList
     *
     * @param field
     * @return
     */
    public static boolean isListEnum(Field field) {
        var type = field.getType();
        return (isCollectionType(type)
                && ((Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]).isEnum())
                ||
                (type.isArray() && type.getComponentType().isEnum());
    }

    public static Boolean isAnyDateOrTimeType(Class clazz) {
        if (clazz == LocalDate.class) return true;
        if (clazz == LocalDateTime.class) return true;
        if (clazz == LocalTime.class) return true;
        if (Date.class.isAssignableFrom(clazz)) return true;
        if (Calendar.class.isAssignableFrom(clazz)) return true;
        if (Temporal.class.isAssignableFrom(clazz)) return true;

        return false;
    }

    public static Boolean isSimpleType(Class clazz) {
        if (clazz.isPrimitive()) return true;
        if (clazz.isEnum()) return true;

        if (isNumberType(clazz)) {
            return true;
        }

        if (clazz == String.class) return true;
        if (clazz == java.lang.Character.class) return true;
        if (clazz == java.lang.Boolean.class) return true;
        if (isAnyDateOrTimeType(clazz)) return true;
        if (clazz == UUID.class) return true;


        return false;
    }

    public static Boolean isStatic(Field field) {
        return Modifier.isStatic(field.getModifiers());
    }

    public static Boolean isTransient(Field field) {
        return Modifier.isTransient(field.getModifiers());
    }


    public static List<Field> getAllFields(Class clazz) {
        var ret = new ArrayList<Field>();
        if (isSimpleType(clazz)) return ret;

        if (
                isCollectionType(clazz) ||
                        Map.class.isAssignableFrom(clazz)
        ) {
            return ret;
        }

        //如果是Map

        var fields = Arrays.stream(clazz.getDeclaredFields())
                .filter(it -> {
                    if (isStatic(it)) return false;
                    if (isTransient(it)) return false;

                    it.setAccessible(true);

                    return true;
                }).collect(Collectors.toList());

        ret.addAll(fields);

        var superClass = clazz.getSuperclass();
        if (superClass == null || superClass == Object.class) {
            return ret;
        }

        var allKeys = ret.stream().map(it -> it.getName()).collect(Collectors.toList());

        ret.addAll(getAllFields(superClass).stream().filter(it -> allKeys.contains(it.getName()) == false).collect(Collectors.toList()));
        return ret;
    }


    public static Object getPrivatePropertyValue(Object entity, String... properties) {
        if (entity == null) return null;
        if (ListUtil.any(properties) == false) return null;

        Field type = null;
        try {
            type = entity.getClass().getDeclaredField(properties[0]);
        } catch (NoSuchFieldException e) {
            return null;
        }

        Object ret = null;
        try {
            ret = type.get(entity);
        } catch (IllegalAccessException e) {
            return null;
        }
        if (properties.length == 1) {
            return ret;
        }

        var leftProperties = Arrays.asList(properties).subList(1, properties.length);
        return getPrivatePropertyValue(ret, leftProperties.toArray(new String[leftProperties.size()]));
    }


    @SneakyThrows
    public static Object getPrivatePropertyValue(Object entity, Field field) {
        field.setAccessible(true);

        return field.get(entity);

    }


    public static Object getValueByWbsPath(
            Object data,
            String... keys
    ) {
        if (keys.length == 0) return null;

        var spreadkeys = Arrays.asList(keys)
                .stream()
                .map(it -> it.split("\\."))
                .collect(Collectors.toList());

        var unwindKeys = ListUtil.unwindWithArray(spreadkeys)
                .stream()
                .map(it -> it.trim())
                .filter(it -> !Strings.isNullOrEmpty(it))
                .collect(Collectors.toList());

        if (unwindKeys.size() != keys.length) {
            return getValueByWbsPath(
                    data,
                    unwindKeys.toArray(new String[unwindKeys.size()])
            );
        }

        var key = keys[0];
        var left_keys = Arrays.asList(keys).stream().skip(1).collect(Collectors.toList());

        if (key.isEmpty()) {
            return null;
        }

        var isLastKey = left_keys.size() == 0;

        if (key.endsWith("]")) {
            if (key != "[]" && key.endsWith("[]")) {
                var keys2 = new ArrayList<String>();
                keys2.add(key.substring(0, key.length() - 2));
                keys2.add("[]");
                keys2.addAll(left_keys);

                return getValueByWbsPath(
                        data,
                        keys2.toArray(new String[keys2.size()])
                );
            }
            var start_index = key.lastIndexOf('[');
            if (start_index > 0) {
                var keys2 = new ArrayList<String>();
                keys2.add(key.substring(0, start_index));
                keys2.add(key.substring(start_index));
                keys2.addAll(left_keys);
                return getValueByWbsPath(
                        data,
                        keys2.toArray(new String[keys2.size()])
                );
            }
        }

        if (data instanceof Map) {
            var map = (Map) data;
            String finalKey = key;
            var vbKeys = (List) map.keySet().stream().filter(it -> it.toString().compareTo(finalKey) == 0).collect(Collectors.toList());

            if (vbKeys.size() > 1) {
                throw new RuntimeException(MessageFormat.format("找到多个 key: {0}", key));
            } else if (vbKeys.size() == 0) {
                return null;
            } else {
                key = vbKeys.get(0).toString();
            }

            var v = map.get(key);

            if (v == null) {
                return null;
            }

            if (left_keys.size() == 0) return v;

            return getValueByWbsPath(
                    v,
                    left_keys.toArray(new String[left_keys.size()])
            );
        } else if (key == "[]") {
            List data2;
            if (data instanceof Array) {
                data2 = ListUtil.fromArray((Array) data).stream().filter(it -> it != null).collect(Collectors.toList());
            } else if (data instanceof Collection) {
                data2 = (List) ((Collection) data).stream().filter(it -> it != null).collect(Collectors.toList());
            } else {
                throw new RuntimeException(MessageFormat.format("数据类型不匹配, {0} 需要是数组类型", key));
            }

            if (ListUtil.any(left_keys) == false) return data2;

            return data2.stream()
                    .map(it ->
                            getValueByWbsPath(
                                    it,
                                    left_keys.toArray(new String[left_keys.size()])
                            )
                    )
                    .filter(it -> it != null)
                    .collect(Collectors.toList());

        } else if (key.startsWith("[") && key.endsWith("]")) {
            var index = StringUtil.asInt(key.substring(1, key.length() - 1), -1);
            if (index < 0) {
                throw new RuntimeException(MessageFormat.format("索引值错误:{0}", key));
            }

            Object data2;
            if (data instanceof Array) {
                data2 = Array.get(data, index);
            } else if (data instanceof Collection) {
                //数组的数组很麻烦

                data2 = ListUtil.elementAt((Collection) data, index);
            } else {
                throw new RuntimeException(MessageFormat.format("需要数组类型,但是实际类型是{0}, key: {1}", data.getClass().getName(), key));
            }

            if (data2 == null) {
                return null;
            }

            if (ListUtil.any(left_keys) == false) return data2;

            return getValueByWbsPath(
                    data2,
                    left_keys.toArray(new String[left_keys.size()])
            );
        }

        //如果是对象

        var v = getPrivatePropertyValue(data, key);
        if (v == null) return null;
        if (ListUtil.any(left_keys) == false) {
            return v;
        }

        return getValueByWbsPath(
                v,
                left_keys.toArray(new String[left_keys.size()])
        );
    }


    public static Boolean isListType(Field field, String clazz) {
//        var clazz = T.class;
        return (
                ClassUtil.isCollectionType(field.getType()) &&
                        ClassUtil.isType((Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0], clazz)

        ) ||
                (field.getType().isArray() && ClassUtil.isType(field.getType().getComponentType(), clazz));

    }


}
